package baseFx.common.config;

import baseFx.common.utils.ClassUtils;
import baseFx.common.utils.ConvertUtils;
import baseFx.common.utils.StringUtils;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ConfigFactory {
    private static List<ConfigReader> readers = new ArrayList<>();
    private final static Map<Class<?>, Object> configs = new HashMap<>();

    public static void addReader(ConfigReader reader) {
        readers.add(reader);
    }

    public static <T> T get(Class<T> clazz) {
        if (configs.containsKey(clazz) == false) {
            synchronized (configs) {
                configs.put(clazz, createProxy(clazz));
            }
        }
        return (T) configs.get(clazz);
    }

    public static void reload() {
        for (ConfigReader reader : readers) {
            reader.reload();
        }
    }

    private static Object createProxy(Class<?> clazz) {
        MethodProxy handler = new MethodProxy(clazz);
        return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);
    }

    private static class MethodProxy implements InvocationHandler {
        private Class<?> clazz;
        private String namespace;
        private static Method resolveMethod;

        static {
            try {
                resolveMethod = ValueResolver.class.getMethods()[0];
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public MethodProxy(Class<?> clazz) {
            this.clazz = clazz;
            Name name = clazz.getAnnotation(Name.class);
            if (name != null) {
                namespace = name.value();
            } else {
                namespace = clazz.getSimpleName();
            }
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            if (void.class.equals(method.getReturnType()) || method.getDeclaringClass().isInterface() == false) {
                try {
                    return method.invoke(this, args);
                } catch (Throwable t) {
                    throw new RuntimeException(t);
                }
            } else {
                if (method.getReturnType().isPrimitive()) {
                    throw new RuntimeException("返回值不能是基础类型：" + method.getDeclaringClass().getSimpleName() + "." + method.getName());
                }
                return run(proxy, method, args);
            }
        }

        public Object run(Object proxy, Method method, Object[] args) {
            String name = getName(method);
            if (method.getDeclaringClass() == MapConfig.class) {
                if ("getConfigNames".equalsIgnoreCase(method.getName()) && method.getParameterCount() == 0) {
                    List<String> names = new ArrayList<>();
                    for (ConfigReader reader : readers) {
                        names.addAll(reader.getNames(namespace));
                    }
                    return names;
                }
                if ("getConfigValue".equalsIgnoreCase(method.getName()) && method.getParameterCount() == 1 && method.getParameters()[0].getType() == String.class) {
                    name = args[0].toString();
                    for (ConfigReader reader : readers) {
                        String value = reader.read(namespace, name);
                        if (value != null) {
                            return value;
                        }
                    }
                    return null;
                }
                throw new RuntimeException("未知方法：" + name);
            }
            for (ConfigReader reader : readers) {
                String value = reader.read(namespace, name);
                if (value == null) {
                    continue;
                }
                Object rs = null;
                if (ValueResolver.class.isAssignableFrom(clazz)) {
                    resolveMethod = ClassUtils.getMethod(clazz, "resolve");
                    rs = invokeDefaultMethod(proxy, resolveMethod, new Object[]{name, value});
                }
                if (rs == null) {
                    rs = ConvertUtils.change(value, method.getReturnType());
                }
                if (rs == null) {
                    continue;
                }
                if (method.getReturnType().isAssignableFrom(rs.getClass()) == false) {
                    throw new ClassCastException("ValueResolver的返回值类型与定义的返回值类型不匹配：" + rs.getClass().getSimpleName() + " , " + method.getReturnType().getSimpleName());
                }
                return rs;
            }
            return isDefaultMethod(method) ? invokeDefaultMethod(proxy, method, args) : null;
        }

        private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) {
            try {
                final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                        .getDeclaredConstructor(Class.class, int.class);
                if (!constructor.isAccessible()) {
                    constructor.setAccessible(true);
                }
                final Class<?> declaringClass = method.getDeclaringClass();
                return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
                        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
            } catch (Throwable throwable) {
                return null;
            }
        }

        private boolean isDefaultMethod(Method method) {
            return ((method.getModifiers()
                    & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
                    && method.getDeclaringClass().isInterface();
        }

        private String getName(Method method) {
            Name name = method.getAnnotation(Name.class);
            if (name != null) {
                return name.value();
            } else {
                String mName = method.getName();
                if (mName.startsWith("get")) {
                    mName = StringUtils.trimLeft(mName, "get");
                }
                return mName;
            }
        }
    }
}
